#include "nemu.h"

#include "device/mmio.h"
#include "memory/mmu.h"

#define PMEM_SIZE (128 * 1024 * 1024)

#define pmem_rw(addr, type) *(type *)({                                       \
  Assert(addr < PMEM_SIZE, "physical address(0x%08x) is out of bound", addr); \
  guest_to_host(addr);                                                        \
})

uint8_t pmem[PMEM_SIZE];

/* Memory accessing interfaces */

uint32_t paddr_read(paddr_t addr, int len)
{
  int tmp = is_mmio(addr);
  if (tmp == -1)
    return pmem_rw(addr, uint32_t) & (~0u >> ((4 - len) << 3));
  else
    return mmio_read(addr, len, tmp);
}

void paddr_write(paddr_t addr, int len, uint32_t data)
{
  int tmp = is_mmio(addr);
  if (tmp == -1)
  {
    memcpy(guest_to_host(addr), &data, len);
    return;
  }
  else
  {
    mmio_write(addr, len, data, tmp);
    return;
  }
}

paddr_t page_translate(vaddr_t addr, bool write)
{
  // no page translate
  if (!cpu.cr0.protect_enable || !cpu.cr0.paging)
  {
    return addr;
  }
  paddr_t paddr = addr, pdep, ptep;
  PDE pde;
  PTE pte;
  pdep = (paddr_t)(cpu.cr3.page_directory_base << 12) | (paddr_t)(((addr >> 22) & 0x3ff) * 4);
  pde.val = paddr_read(pdep, 4);
  if (!pde.present)
    panic("pde not present! with pde = %x[page_translation]\n", pde.val);
  pde.accessed = 1;
  ptep = (paddr_t)(pde.page_frame << 12) | (paddr_t)(((addr >> 12) & 0x3ff) * 4);
  pte.val = paddr_read(ptep, 4);
  if (!pte.present)
    panic("pte not present! with pte = %x[page_translation]\n", pte.val);
  pte.accessed = 1;
  pte.dirty = (write == true) ? 1 : 0;
  paddr = (paddr_t)(pte.page_frame << 12) | (paddr_t)(addr & 0xfff);
  return paddr;
}

uint32_t vaddr_read(vaddr_t addr, int len)
{
  bool is_cross = (addr >> 12 != (addr + len - 1) >> 12);
  if (is_cross)
  {
    int len1 = PAGE_SIZE - ((addr<<20)>>20);
    int len2 = len - len1;
    uint32_t val1 = paddr_read(page_translate(addr, false), len1);
    uint32_t val2 = paddr_read(page_translate(addr + len1, false), len2);
    return val1 | val2<<(len1<<3);
  }
  else
  {
    paddr_t paddr = page_translate(addr, false);
    return paddr_read(paddr, len);
  }
}

void vaddr_write(vaddr_t addr, int len, uint32_t data)
{
  bool is_cross = (addr >> 12 != (addr + len - 1) >> 12);
  if (is_cross)
  {
    int len1 = PAGE_SIZE - ((addr<<20)>>20);
    int len2 = len - len1;
    uint32_t addr1 = page_translate(addr, true);
    uint32_t addr2 = page_translate(addr + len1, true);
    paddr_write(addr1, len1, (data<<(32-len1*8))>>(32-len1*8));
    paddr_write(addr2, len2, data>>(32-len1*8));
  }
  else
  {
    paddr_t paddr = page_translate(addr, true);
    paddr_write(paddr, len, data);
  }
}
